/**
 * Cross-browser event class.
 *
 * @author Andrey Anisimov <hercules@inbox.ru>
 */

class Event {
	
	/**
	 * Initializes the new event.
	 */
	this.Event = function() {
		this.stopped = false;
		this.cancelled = false;
	}

	/**
	 * Prevents further propagation of the current event.
	 */
	this.stop = function() {
		if (undefined !== this.stopPropagation) {
			this.stopPropagation();
		} else if (undefined !== this.cancelBubble) {
			this.cancelBubble = true;
		}
		this.stopped = true;
	}
	
	/**
	 * Gets whether the event propagation is stopped.
	 *
	 * @return bool
	 */
	this.isStopped = function() {
		return this.stopped;
	}
	
	/**
	 * Cancels the event, without stopping further propagation of the event.
	 * Calling cancel() during any stage of event flow cancels the event,
	 * meaning that any default action normally taken by the implementation
	 * as a result of the event will not occur.
	 */
	this.cancel = function() {
		if (undefined !== this.preventDefault) {
			this.preventDefault();
		} else if (undefined !== this.returnValue) {
			this.returnValue = false;
		}
		this.cancelled = true;
	}
	
	/**
	 * Checks whether the event is cancelled.
	 *
	 * @return bool
	 */
	this.isCancelled = function() {
		return this.cancelled;
	}
	
	/**
	 * Returns type of event (e.g. mousedown, mouseup, click etc).
	 *
	 * @return string
	 */
	this.getType = function() {
		return this.type;
	}

	/**
	 * Returns the target element.
	 *
	 * @return HTMLElement
	 */
	this.getTarget = function() {
		return (this.target || this.srcElement);
	}
	
	/**
	 * Returns x coordinate of mouse event.
	 *
	 * @return int
	 */
	this.getX = function() {
		return this.clientX + document.body.scrollLeft;
	}
	
	this.getScreenX = function() {
		return this.screenX + document.body.scrollLeft;
	}
	
	this.getScreenY = function() {
		return this.screenY + document.body.scrollTop;
	}
	
	/**
	 * Returns y coordinate of mouse event.
	 *
	 * @return int
	 */
	this.getY = function() {
		return this.clientY + document.body.scrollTop;
	}
	
	/**
	 * Returns character typed by user (for keyboard events only).
	 *
	 * @return string
	 */
	this.getChar = function() {
		return String.fromCharCode(this.keyCode || this.charCode).toUpperCase();
	}
	
	this.toString = function() {
		return '[Event type="' + this.type + '"]';
	}
}

/**
 * Adds common properties and methods to event.
 * This helps to process ecent commonly in most known browsers.
 *
 * Note: if no event parameter is specified, then this method
 * will use window.event object (which is available only in IE).
 * 
 * @param event (object) [optional] borwser-specific event.
 * @return Event
 */
Event.improve = function(event) {
	if (!event) event = window.event;
    Event.class.applyMembers(event);
	return event;
}

/**
 * Adds new listener function for the event.
 *
 * Note: the callback function will be invoked when the event fires
 * with one argument: event object which is type of Event.
 *
 * @param el (Node) DOM-element that produces events
 * @param type (string) event type (without "on" prefix)
 * @param callback (function) event handler
 */
Event.addListener = function(el, type, callback) {
	if (undefined !== el.attachEvent) {
		el.attachEvent('on' + type, callback);
	} else if (undefined !== el.addEventListener) {
		el.addEventListener(type, callback, false);
	} else {
		// TODO: save old listener
		el['on' + type] = callback;
	}
}

/**
 * Removes listener function from the event handlers.
 * 
 * @param el (Node) DOM-element that produces events
 * @param type (string) event type (without "on" prefix)
 * @param callback (function) event handler
 */
Event.removeListener = function(el, type, callback) {
	if (undefined !== el.detachEvent) {
		el.detachEvent('on' + type, callback);
	} else if (undefined !== el.removeEventListener) {
		el.removeEventListener(type, callback, false);
	} else {
		// TODO: implement
	}
}

/**
 * Add new listener function for document 'onload' event.
 *
 * @param callback (function) callback function.
 */
Event.addLoadListener = function(callback) {
	Event.addListener(window, 'load', callback);
}

/**
 * Removes listener for 'onload' event.
 *
 * @param callback (function) callback function.
 */
Event.removeLoadListener = function(callback) {
	Event.removeListener(window, 'load', callback);
}

/**
 * Add new listener function for document 'onunload' event.
 *
 * @param callback (function) callback function.
 */
Event.addUnloadListener = function(callback) {
	Event.addListener(window, 'unload', callback);
}

/**
 * Removes listener for 'onunload' event.
 *
 * @param callback (function) callback function.
 */
Event.removeUnloadListener = function(callback) {
	Event.removeListener(window, 'unload', callback);
}

/**
 * Natively supported event types.
 *
 * @type array of string
 */
Event.types = [
	'blur',
	'change',
	'click',
	'contextmenu',	// TODO: emulate this event
	'dblclick',
	'focus',
	// Key events
	'keydown',
	'keypress',
	'keyup',
	'load',
	// Mouse events
	'mousedown',
	'mousemove',
	'mouseout',
	'mouseover',
	'mouseup',
	'select',
	'scroll',
	'unload'
];

// Key codes
Event.KEY_BACKSPACE = 8;
Event.KEY_TAB = 9;
Event.KEY_ENTER = 13;
Event.KEY_SHIFT = 16;
Event.KEY_CTRL = 17;
Event.KEY_ALT = 18;
Event.KEY_PAUSE = 19;
Event.KEY_CAPS_LOCK = 20;
Event.KEY_ESCAPE = 27;
Event.KEY_SPACE = 32;
Event.KEY_PAGE_UP = 33;
Event.KEY_PAGE_DOWN = 34;
Event.KEY_END = 35;
Event.KEY_HOME = 36;
Event.KEY_LEFT_ARROW = 37;
Event.KEY_UP_ARROW = 38;
Event.KEY_RIGHT_ARROW = 39;
Event.KEY_DOWN_ARROW = 40;
Event.KEY_INSERT = 45;
Event.KEY_DELETE = 46;
Event.KEY_LEFT_WINDOW = 91;
Event.KEY_RIGHT_WINDOW = 92;
Event.KEY_SELECT = 93;
Event.KEY_F1 = 112;
Event.KEY_F2 = 113;
Event.KEY_F3 = 114;
Event.KEY_F4 = 115;
Event.KEY_F5 = 116;
Event.KEY_F6 = 117;
Event.KEY_F7 = 118;
Event.KEY_F8 = 119;
Event.KEY_F9 = 120;
Event.KEY_F10 = 121;
Event.KEY_F11 = 122;
Event.KEY_F12 = 123;
Event.KEY_NUM_LOCK = 144;
Event.KEY_SCROLL_LOCK = 145;